package org.rambadger.command.providers;


import com.google.common.primitives.Ints;
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;

import javax.annotation.Nullable;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import java.lang.reflect.Type;

/**
 * Jersey provider that injects a {@link Pagination} object configured from the query parameters
 */
public class PaginationProvider implements InjectableProvider<Context, Type> {
    /**
     * Instantiates a new Pagination provider.
     */
    public PaginationProvider() {}

    @Override
    public ComponentScope getScope() {
        return ComponentScope.PerRequest;
    }

    @Override
    public Injectable<Pagination> getInjectable(ComponentContext ic, Context injectParam, Type c) {
        if (c.equals(Pagination.class)) {
            return new AbstractHttpContextInjectable<Pagination>() {
                @Override
                public Pagination getValue(HttpContext c) {
                    MultivaluedMap<String, String> parameters = c.getUriInfo().getQueryParameters();
                    //The number must be positive
                    int page = getParameter(parameters, "page", 1, 1, null, null);
                    //Restrict the value to one of 5,10,15,25,50,75,100
                    int perPage = getParameter(parameters, "per_page", 15, 5, 100,  new int[]{5,10,15,25,50,75,100});
                    return new Pagination(page, perPage);
                }

            };
        } else {
            return null;
        }
    }

    /**
     * Resolves the integer value for a parameter based on several constraints.
     *
     * @param parameters {@link MultivaluedMap} containing the query parameters for a request
     * @param key the name of the query parameter. If multiple query parameters of the same name are present, only the first one returned by the map gets considered
     * @param defaultValue value to use if the value found in the query parameter either fails to parse, or does not meet one or more of the constraints
     * @param min optional constraint setting a minimum allowed value. Pass {@code null} to ignore the constraint
     * @param max optional constraint setting a maximum allowed value. Pass {@code null} to ignore the constraint
     * @param validValues optional array of {@code int}s. If not null, the value must be equal to one of the values in the array
     * @return the resolved value
     */
    private int getParameter(MultivaluedMap<String, String> parameters, String key, int defaultValue,
                             @Nullable Integer min,
                             @Nullable Integer max,
                             @Nullable int[] validValues) {
        if (parameters.containsKey(key)) {
            try {
                int found = Integer.valueOf(parameters.getFirst(key));
                //Check bounds
                if (min != null && min > found) return defaultValue;
                if (max != null && max < found) return defaultValue;
                if (validValues != null) {
                    //Check the array
                    return Ints.contains(validValues, found)?found:defaultValue;
                } else {
                    return found;
                }
            } catch (NumberFormatException e) {
                return defaultValue;
            }
        } else {
            return defaultValue;
        }
    }
}

